﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WorkflowModel.cs" company="Microsoft">
//   Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace TrackingStateMachine
{
    using System;
    using System.Activities;
    using System.Activities.Statements;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Runtime.DurableInstancing;
    using System.Threading.Tasks;

    using Microsoft.Activities.Extensions.Tracking;

    /// <summary>
    ///   The WorkflowModel class invokes the workflows
    /// </summary>
    public sealed class WorkflowModel : INotifyPropertyChanged, IDisposable
    {
        #region Fields

        /// <summary>
        ///   Waits on the closing tasks
        /// </summary>
        private readonly TaskWaiter closingTasks = new TaskWaiter();

        /// <summary>
        ///   The view.
        /// </summary>
        private readonly IWorkflowView view;

        /// <summary>
        ///   The selected index.
        /// </summary>
        private int selectedIndex = -1;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowModel"/> class.
        /// </summary>
        /// <param name="view">
        /// The view. 
        /// </param>
        internal WorkflowModel(IWorkflowView view)
        {
            this.view = view;
            this.Workflows = new ObservableCollection<WorkflowInstance>();
        }

        #endregion

        #region Public Events

        /// <summary>
        ///   The property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets CurrentInstance.
        /// </summary>
        public WorkflowInstance CurrentInstance
        {
            get
            {
                Debug.Assert(this.Workflows != null, "Workflows is null");
                return this.SelectedIndex != -1 ? this.Workflows[this.SelectedIndex] : null;
            }
        }

        /// <summary>
        ///   Gets CurrentState.
        /// </summary>
        public string CurrentState
        {
            get
            {
                return this.CurrentStateTracker != null ? this.CurrentStateTracker.CurrentState : null;
            }
        }

        /// <summary>
        ///   Gets CurrentStateMachineName.
        /// </summary>
        public string CurrentStateMachineName
        {
            get
            {
                return this.CurrentStateTracker != null && this.CurrentStateTracker.CurrentStateMachine != null
                           ? this.CurrentStateTracker.CurrentStateMachine
                           : "Unknown";
            }
        }

        /// <summary>
        ///   Gets CurrentWorkflow.
        /// </summary>
        public WorkflowApplication CurrentWorkflow
        {
            get
            {
                if (this.SelectedIndex == -1)
                {
                    return null;
                }

                Debug.Assert(this.Workflows != null, "Workflows is null");
                var workflowInstance = this.Workflows[this.SelectedIndex];
                Debug.Assert(workflowInstance != null, "workflowInstance is null");
                {
                    return workflowInstance.Host;
                }
            }
        }

        /// <summary>
        ///   Gets a value indicating whether IsLoaded.
        /// </summary>
        public bool IsLoaded
        {
            get
            {
                var workflowInstance = this.CurrentInstance;
                return workflowInstance != null && (this.SelectedIndex != -1 && workflowInstance.IsLoaded);
            }
        }

        /// <summary>
        ///   Gets or sets SelectedIndex.
        /// </summary>
        public int SelectedIndex
        {
            get
            {
                return this.selectedIndex;
            }

            set
            {
                this.selectedIndex = value;
                this.NotifyChanged("SelectedIndex");
            }
        }

        /// <summary>
        ///   Gets Transitions.
        /// </summary>
        public ICollection<Transition> Transitions
        {
            get
            {
                return this.CurrentStateTracker != null ? this.CurrentStateTracker.Transitions : null;
            }
        }

        /// <summary>
        ///   Gets Workflows.
        /// </summary>
        public ObservableCollection<WorkflowInstance> Workflows { get; private set; }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets CurrentStateTracker.
        /// </summary>
        private StateMachineStateTracker CurrentStateTracker
        {
            get
            {
                if (this.SelectedIndex == -1)
                {
                    return null;
                }

                Debug.Assert(this.Workflows != null, "Workflows is null");
                var workflowInstance = this.Workflows[this.SelectedIndex];
                return workflowInstance != null ? workflowInstance.StateTracker : null;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Dispose resources
        /// </summary>
        public void Dispose()
        {
            if (this.closingTasks != null)
            {
                this.closingTasks.Dispose();
            }
        }

        /// <summary>
        ///   The load.
        /// </summary>
        /// <returns> True if loaded, false if not </returns>
        public bool Load()
        {
            var instance = this.CurrentInstance;
            if (instance != null)
            {
                try
                {
                    instance.Load();
                    this.NotifyChanged("Workflows");
                    instance.Run();
                    return true;
                }
                catch (InstancePersistenceException persistenceException)
                {
                    Trace.WriteLine(persistenceException.Message);
                    return false;
                }
            }

            return false;
        }

        /// <summary>
        ///   The on complete.
        /// </summary>
        public void OnComplete()
        {
            Debug.Assert(this.Workflows != null, "Workflows is null");
            this.Workflows.RemoveAt(this.SelectedIndex);
            this.SelectedIndex = this.Workflows.Count - 1;
        }

        /// <summary>
        /// The open.
        /// </summary>
        /// <param name="instanceId">
        /// The instance id. 
        /// </param>
        public void Open(Guid instanceId)
        {
            Debug.Assert(this.view != null, "View is null");
            var sm = StateMachineStateTracker.LoadInstance(
                instanceId, WorkflowInstance.Workflow1Definition, this.view.ConnectionString);
            if (sm == null)
            {
                return;
            }

            Debug.Assert(this.Workflows != null, "Workflows is null");
            this.Workflows.Add(new WorkflowInstance(this.view, sm));
            
            this.NotifyChanged("Workflows");

            Debug.Assert(this.Workflows != null, "Workflows is null");
            this.selectedIndex = this.Workflows.Count - 1;

            Debug.Assert(this.view != null, "View is null");
            this.view.NotifyChanged("SelectedIndex");
        }

        /// <summary>
        /// Resume a bookmark
        /// </summary>
        /// <param name="bookmark">
        /// The bookmark. 
        /// </param>
        public void ResumeBookmark(string bookmark, string auditFlag=null)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(bookmark));
            if (string.IsNullOrWhiteSpace(bookmark))
            {
                throw new ArgumentNullException("bookmark");
            }

            if (!this.IsLoaded)
            {
                if (!this.Load())
                {
                    return;
                }
            }

            Debug.Assert(this.CurrentWorkflow != null, "this.CurrentWorkflow is null");
            this.CurrentWorkflow.ResumeBookmark(bookmark, auditFlag);
        }

        /// <summary>
        ///   The unload.
        /// </summary>
        public void Unload()
        {
            var instance = this.CurrentInstance;
            if (instance != null)
            {
                instance.Unload();
                this.NotifyChanged("Workflows");
            }
        }

        /// <summary>
        /// The view is closed
        /// </summary>
        /// <param name="sender">
        /// The view The sender. 
        /// </param>
        /// <param name="e">
        /// The event args. 
        /// </param>
        public void ViewClosed(object sender, EventArgs e)
        {
            Debug.Assert(this.closingTasks != null, "this.closingTasks is null");
            this.closingTasks.Wait();
        }

        /// <summary>
        /// The view is closing
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="e">
        /// The event args. 
        /// </param>
        public void ViewClosing(object sender, CancelEventArgs e)
        {
            Debug.Assert(this.Workflows != null, "this.Workflows  is null");
            Debug.Assert(this.closingTasks != null, "this.closingTasks is null");
            foreach (var workflowInstance in this.Workflows.Where(w => w != null && w.Host != null))
            {
                Debug.Assert(Task.Factory != null, "Task.Factory != null");
                Debug.Assert(workflowInstance != null, "workflowInstance != null");
                Debug.Assert(workflowInstance.Host != null, "workflowInstance.Host != null");
                this.closingTasks.Add(
                    Task.Factory.FromAsync(workflowInstance.Host.BeginUnload, workflowInstance.Host.EndUnload, null));
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Loads the instances
        /// </summary>
        /// <param name="workflowView">
        /// The view. 
        /// </param>
        internal void LoadInstances(IWorkflowView workflowView)
        {
            Contract.Requires(workflowView != null);
            if (workflowView == null)
            {
                throw new ArgumentNullException("workflowView");
            }

            var instances = StateMachineStateTracker.LoadInstances(
                WorkflowInstance.Workflow1Definition, workflowView.ConnectionString);

            if (instances != null)
            {
                foreach (var stateMachineStateTracker in instances)
                {
                    Debug.Assert(this.Workflows != null, "this.Workflows  is null");
                    this.Workflows.Add(new WorkflowInstance(this.view, stateMachineStateTracker));
                }

                Debug.Assert(this.Workflows != null, "this.Workflows  is null");
                Trace.WriteLine(
                    string.Format(
                        "Loaded {0} instance{1} from instance store", 
                        this.Workflows.Count, 
                        this.Workflows.Count != 1 ? "s" : string.Empty));
            }
            else
            {
                Trace.WriteLine("Error loading instances - be sure to create the SampleInstanceStore database first");
            }
        }

        /// <summary>
        ///   The new.
        /// </summary>
        internal void New()
        {
            var instance = new WorkflowInstance(this.view);
            instance.New();
            Debug.Assert(this.Workflows != null, "this.Workflows  is null");
            this.Workflows.Add(instance);
            instance.Run();
            this.SelectedIndex = this.Workflows.Count - 1;
            Debug.Assert(this.view != null, "View is null");
            this.view.NotifyChanged("SelectedIndex");
        }

        /// <summary>
        /// The on property changed.
        /// </summary>
        /// <param name="propertyName">
        /// The property name. 
        /// </param>
        private void NotifyChanged(string propertyName)
        {
            var onPropertyChanged = this.PropertyChanged;
            if (onPropertyChanged != null)
            {
                onPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}